/**
 * @file      : spi.c
 * @author    : huenrong (huenrong1028@gmail.com)
 * @date      : 2020-05-30 15:30:95
 *
 * @copyright : Copyright (c) 2020  胡恩荣
 *
 */

#include "spi.h"

/**
 * @brief  打开spi设备
 * @param  spi_dev_path: spi设备路径(如: /dev/spidev2.0)
 * @param  spi_mode: spi模式(SPI_MODE_0, SPI_MODE_1, SPI_MODE_2, SPI_MODE_3)
 * @return 成功: 返回设备文件描述符
 *         失败: -1
 */
int spi_open(const char *spi_dev_path, const int spi_mode)
{
    int ret = -1;
    int fd = -1;        // spi设备描述符
    int spi_speed = -1; // spi最大速率
    int spi_bits = -1;  // spi一个字节的位数

    // 打开spi设备
    fd = open(spi_dev_path, O_RDWR);
    if (fd < 0)
    {
        perror("spi open error");

        return -1;
    }

    // 设置spi写模式
    ret = ioctl(fd, SPI_IOC_WR_MODE, &spi_mode);
    if (ret < 0)
    {
        perror("set spi write mode error");
        close(fd);

        return -1;
    }

    // 设置spi读模式
    ret = ioctl(fd, SPI_IOC_RD_MODE, &spi_mode);
    if (ret < 0)
    {
        perror("set spi read mode error");
        close(fd);

        return -1;
    }

    // 设置spi写最大速率
    spi_speed = SPI_SPEED;
    ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
    if (ret < 0)
    {
        perror("set spi write speed error");
        close(fd);

        return -1;
    }

    // 设置spi读最大速率
    ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &spi_speed);
    if (ret < 0)
    {
        perror("set spi read speed error");
        close(fd);

        return -1;
    }

    // 设置写一个字节的位数
    spi_bits = SPI_BITS_PER_WORD;
    ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits);
    if (ret < 0)
    {
        perror("set spi write per word error");
        close(fd);

        return -1;
    }

    // 设置读一个字节的位数
    ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &spi_bits);
    if (ret < 0)
    {
        perror("set spi read per word error");
        close(fd);

        return -1;
    }

    return fd;
}

/**
 * @brief  关闭spi设备
 * @param  fd: 设备文件描述符
 * @return 成功: 0
 *         失败: -1
 */
int spi_close(const int fd)
{
    int ret = -1;

    ret = close(fd);

    return ret;
}

/**
 * @brief  spi发送1字节数据
 * @param  fd: 设备文件描述符
 * @param  reg_addr: 寄存器地址
 * @param  write_data: 发送数据
 * @return 成功: 0
 *         失败: -1
 */
int spi_write_byte(const int fd, const uint8_t reg_addr, const uint8_t write_data)
{
    int ret = -1;
    uint8_t write_buf[3] = {0}; // 要发送的寄存器地址+数据
    struct spi_ioc_transfer spi_transfer = {0};

    // spi写数据时, 先发送要写入的寄存器地址, 再发送要写入的数据

    // 寄存器地址
    write_buf[0] = reg_addr;
    // 要写入的数据
    write_buf[1] = write_data;

    // spi传输结构体赋值
    memset(&spi_transfer, 0, sizeof(spi_transfer));
    spi_transfer.tx_buf = (unsigned long)write_buf; // 发送的寄存器地址+数据
    spi_transfer.len = 2;                           // 缓冲长度(发送数据长度)
    spi_transfer.cs_change = 0;                     // cs_change结束之后是否需要改变片选线, 一般在用户空间控制

    // 发送数据
    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &spi_transfer);
    // 发送失败
    if (-1 == ret)
    {
        perror("spi write byte error");

        return -1;
    }

    return 0;
}

/**
 * @brief  spi发送n字节数据(n > 1)
 * @param  fd: 设备文件描述符
 * @param  reg_addr: 寄存器地址
 * @param  write_data: 发送数据
 * @param  write_data_len: 发送数据长度
 * @return 成功: 0
 *         失败: -1
 */
int spi_write_nbyte(const int fd, const uint8_t reg_addr,
                    const uint8_t *write_data, const uint32_t write_data_len)
{
    int ret = -1;
    uint32_t i = 0;
    uint8_t write_buf[2] = {0};
    uint32_t remain_data_len = 0;  // 未发送数据长度
    uint32_t current_data_len = 0; // 本次发送数据长度
    uint32_t data_offset = 0;      // 本次发送数据偏移量
    int transfered_data_len = 0;   // 累计传输数据长度
    struct spi_ioc_transfer spi_transfer[2];

    // spi写数据时, 先发送要写入的寄存器地址, 再发送要写入的数据

    // 寄存器地址
    write_buf[0] = reg_addr;

    // 未发送数据长度
    remain_data_len = write_data_len;

    // spi_transfer[0]发送要写入的寄存器地址
    memset(&spi_transfer, 0, sizeof(spi_transfer));
    spi_transfer[0].tx_buf = (unsigned long)&write_buf[0];
    spi_transfer[0].len = 1;
    spi_transfer[0].cs_change = 0;

    // 循环发送数据
    // spi_transfer[1]发送数据
    for (i = 0; remain_data_len > 0; ++i)
    {
        // 计算本次发送数据长度(未发送数据长度和单次最大传输长度比较)
        current_data_len = ((remain_data_len < SPI_MAX_TRANSFER_LEN) ? remain_data_len : SPI_MAX_TRANSFER_LEN);

        // 计算本次发送数据偏移量
        data_offset = (i * SPI_MAX_TRANSFER_LEN);

        // spi传输结构体赋值
        spi_transfer[1].tx_buf = (unsigned long)(write_data + data_offset);
        spi_transfer[1].len = current_data_len;
        spi_transfer[1].cs_change = 0;

        // 发送数据
        // byte_transfered += (ioctl(fd, SPI_IOC_MESSAGE(2), &spi_transfer) - spi_transfer[0].len);

        // 发送数据
        ret = ioctl(fd, SPI_IOC_MESSAGE(2), &spi_transfer);
        // 发送失败
        if (-1 == ret)
        {
            perror("spi write n byte error");

            return -1;
        }
        // 发送成功
        else
        {
            // 计算累计传输数据长度
            transfered_data_len += (ret - spi_transfer[0].len);

            // 计算未发送数据长度
            remain_data_len -= current_data_len;
        }
    }

    // 数据未全部发送
    if (write_data_len != transfered_data_len)
    {
        return -1;
    }

    return 0;
}

/**
 * @brief  spi读取1字节数据
 * @param  read_data: 读取到的数据
 * @param  fd: 设备文件描述符
 * @param  reg_addr: 寄存器地址
 * @return 成功: 0
 *         失败: -1
 */
int spi_read_byte(uint8_t *read_data, const int fd, const uint8_t reg_addr)
{
    int ret = -1;
    uint8_t write_buf[1] = {0};
    struct spi_ioc_transfer spi_transfer[2];

    // spi读数据时, 先发送要读取的寄存器地址, 再读取数据

    // 寄存器地址
    write_buf[0] = reg_addr;

    // 半双工模式, 先发送, 再读取
    memset(&spi_transfer, 0, sizeof(spi_transfer));
    // spi_transfer[0]发送要读取的寄存器地址
    spi_transfer[0].tx_buf = (unsigned long)write_buf;
    spi_transfer[0].len = 1;
    spi_transfer[0].cs_change = 0;

    // spi_transfer[1]读取数据
    memset(read_data, 0, 1);
    spi_transfer[1].rx_buf = (unsigned long)read_data;
    spi_transfer[1].len = 1; // 指定读取长度
    spi_transfer[1].cs_change = 0;

    ret = ioctl(fd, SPI_IOC_MESSAGE(2), &spi_transfer);
    if (-1 == ret)
    {
        perror("spi read byte error");

        return -1;
    }

    return 0;
}

/**
 * @brief  spi读取n字节数据(n > 1)
 * @param  read_data: 读取到的数据
 * @param  fd: 设备文件描述符
 * @param  reg_addr: 寄存器地址
 * @param  read_data_len: 指定读取的长度
 * @retval 成功: 0
 *         失败: -1
 */
int spi_read_nbyte(uint8_t *read_data, const int fd,
                   const uint8_t reg_addr, const uint16_t read_data_len)
{
    int ret = -1;
    uint32_t i = 0;
    uint8_t write_buf[2] = {0};
    uint32_t remain_data_len = 0;  // 未读取数据长度
    uint32_t current_data_len = 0; // 本次读取数据长度
    uint32_t data_offset = 0;      // 本次读取数据偏移量
    int transfered_data_len = 0;   // 累计传输数据长度
    struct spi_ioc_transfer spi_transfer[2];

    // spi读数据时, 先发送要读取的寄存器地址, 再读取数据

    // 寄存器地址(最高位为0, 表示读数据)
    write_buf[0] = reg_addr;

    // 未读取数据长度
    remain_data_len = read_data_len;

    // 半双工模式, 先发送, 再读取
    memset(&spi_transfer, 0, sizeof(spi_transfer));
    // spi_transfer[0]发送要读取的寄存器地址
    spi_transfer[0].tx_buf = (unsigned long)&write_buf[0];
    spi_transfer[0].len = 1;
    spi_transfer[0].cs_change = 0;

    // 循环读取数据
    // spi_transfer[1]读取数据
    for (i = 0; remain_data_len > 0; ++i)
    {
        // 计算本次读取数据长度(未发送数据长度和单次最大传输长度比较)
        current_data_len = (remain_data_len < SPI_MAX_TRANSFER_LEN) ? remain_data_len : SPI_MAX_TRANSFER_LEN;

        // 计算本次读取数据偏移量
        data_offset = (i * SPI_MAX_TRANSFER_LEN);

        // spi传输结构体赋值
        spi_transfer[1].rx_buf = (unsigned long)(read_data + data_offset);
        spi_transfer[1].len = current_data_len;
        spi_transfer[1].cs_change = 0;

        // 读取数据
        ret = ioctl(fd, SPI_IOC_MESSAGE(2), &spi_transfer);
        // 读取失败
        if (-1 == ret)
        {
            perror("spi write n byte error");

            return -1;
        }
        // 读取成功
        else
        {
            // 计算累计传输数据长度
            transfered_data_len += (ret - spi_transfer[0].len);

            // 计算未发送数据长度
            remain_data_len -= current_data_len;
        }
    }

    // 数据未全部读取
    if (transfered_data_len != read_data_len)
    {
        return -1;
    }

    return 0;
}
